﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
using System.Web.UI;

namespace CloudStorageLight.Core.Web
{
    public abstract class BaseHttpApplication : System.Web.HttpApplication
    {

        protected void ShortTermJob()
        {
            var flushlogJob = new ThreadStart(() =>
            {
                while (true)
                {
                    try
                    {
                        var svc = new LogService(SystemSettings.Instance.BlobStorage);
                        svc.FlushLog(this.Application);
                        if (SystemSettings.Instance.FullTextServerRole)
                        {
                            FarmControl.WritePrimaryServerHaertBeat(SystemSettings.Instance.BlobStorage);
                        }
                    }
                    catch (Exception ex)
                    {
                        LogError(null, ex.ToString());
                    }

                    Thread.Sleep(10000);
                }
            });
            new Thread(flushlogJob).Start();
        }

        protected void MiddleTermJob()
        {
            var storageDict = new Dictionary<string, long>();
            this.Application["UsedStoargeSize"] = storageDict;
            var lastRenew = new Dictionary<string, DateTime>();

            var timerJob = new ThreadStart(() =>
            {
                while (true)
                {
                    try
                    {
                        if (!string.IsNullOrEmpty(SystemSettings.Instance.BlobStorage))
                        {
                            List<CloudAccount> list = new List<CloudAccount>();
                            list.AddRange(CloudAccount.Load(SystemSettings.Instance.BlobStorage));

                            foreach (var account in list)
                            {
                                try
                                {
                                    using (var svc = new BlobService(account, this.Application))
                                    {
                                        UpdateStorageSizeInfo(storageDict, lastRenew, account, svc);
                                        var cacheKey = account.AccountName + "_UserRoleList";
                                        if (account.AzureAdMode)
                                        {
                                            var authority = account.Authority;
                                            var cloudStorageLightAppKey = account.CloudStorageLightAppKey;
                                            var cloudStorageLightClientId = account.CloudStorageLightClientId;
                                            var tenantId = account.TenantId;
                                            var glist = AzureAdUtil.ListGroup(authority, cloudStorageLightClientId, cloudStorageLightAppKey, tenantId).Select(x => x.DisplayName).OrderBy(x => x).ToList();
                                            Application[cacheKey] = glist;
                                            LogInfo(account, "ADグループ一覧作成完了 : " + account.AccountName + "  " + glist.Count().ToString());
                                        }

                                        if (FarmControl.IsPrimaryServer(SystemSettings.Instance.BlobStorage))
                                        {
                                            svc.SendNotifyMail().Wait();
                                            EnsureFileAccessLog(account, svc);
                                            if (account.LogRetentionDays > 0)
                                            {
                                                svc.LogService.DeleteLog(account.AccountName, DateTime.Now.AddDays(-1 * account.LogRetentionDays)).Wait();
                                            }
                                        }
                                    }
                                }
                                catch (Exception ex)
                                {
                                    LogError(account, ex.ToString());
                                }
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        LogError(null, ex.ToString());
                    }
                    Thread.Sleep(new TimeSpan(0, 15, 0));
                }
            });
            new Thread(timerJob).Start();
        }

        private static void UpdateStorageSizeInfo(Dictionary<string, long> storageDict, Dictionary<string, DateTime> lastRenew, CloudAccount account, BlobService svc)
        {
            if (!lastRenew.ContainsKey(account.AccountName) || lastRenew[account.AccountName].AddHours(1) < DateTime.UtcNow)
            {
                svc.RenewSearcherIndex();
                lastRenew[account.AccountName] = DateTime.UtcNow;

                storageDict[account.AccountName] = svc.GetUsedStorageSize().Result;
            }
        }

        private static void EnsureFileAccessLog(CloudAccount account, BlobService svc)
        {
            var logSvc = new LogService(SystemSettings.Instance.BlobStorage);
            logSvc.EnsureFileAccessLog(account.AccountName).Wait();
        }

        protected void FullTextServerRoleJob()
        {
            var fulltextentryJob = new ThreadStart(() =>
            {
                Thread.Sleep(new TimeSpan(0, 3, 0)); // 初期起動時の緩和
                var lastRenew = new Dictionary<string, DateTime>();
                while (true)
                {
                    try
                    {
                        if (!string.IsNullOrEmpty(SystemSettings.Instance.BlobStorage))
                        {
                            if (FarmControl.IsPrimaryServer(SystemSettings.Instance.BlobStorage))
                            {
                                List<CloudAccount> list = new List<CloudAccount>();
                                list.AddRange(CloudAccount.Load(SystemSettings.Instance.BlobStorage));

                                foreach (var account in list)
                                {
                                    try
                                    {
                                        using (var svc = new BlobService(account, this.Application))
                                        {
                                            svc.ExecuteUpdateFullText(100).Wait();
                                        }
                                    }
                                    catch (Exception ex)
                                    {
                                        LogError(account, ex.ToString());
                                    }
                                }
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        LogError(null, ex.ToString());
                    }
                    Thread.Sleep(30000);
                }
            });
            new Thread(fulltextentryJob).Start();

        }

        protected void CrawlJob()
        {

            for (int i = 0; i < SystemSettings.Instance.FullTextWorkerThreadCount; i++)
            {
                var crawlJob = new ThreadStart(() =>
                {
                    Thread.Sleep(new TimeSpan(0, 10, 0)); // 初期起動時の緩和
                    while (true)
                    {
                        try
                        {
                            if (!string.IsNullOrEmpty(SystemSettings.Instance.BlobStorage))
                            {
                                List<CloudAccount> list = new List<CloudAccount>();
                                list.AddRange(CloudAccount.Load(SystemSettings.Instance.BlobStorage));

                                foreach (var account in list)
                                {
                                    try
                                    {
                                        using (var svc = new BlobService(account, this.Application))
                                        {
                                            while (svc.CrawlAsync().Result) ;
                                        }
                                    }
                                    catch (Exception ex)
                                    {
                                        LogError(account, ex.ToString());
                                    }
                                }
                            }
                        }
                        catch (Exception ex)
                        {
                            LogError(null, ex.ToString());
                        }
                        Thread.Sleep(5000);
                    }
                });
                new Thread(crawlJob).Start();
            }
        }


        private void LogError(CloudAccount account, string message)
        {
            try
            {
                var svc = new LogService(SystemSettings.Instance.BlobStorage);
                svc.EntryLog(HttpContext.Current.User.Identity.Name, account == null ? "" : account.AccountName, "Error", this.Request.Url.PathAndQuery, message, this.Application);
            }
            catch
            {
            }
        }
        private void LogInfo(CloudAccount account, string message)
        {
            try
            {
                var svc = new LogService(SystemSettings.Instance.BlobStorage);
                svc.EntryLog(HttpContext.Current.User.Identity.Name, account == null ? "" : account.AccountName, "Info", this.Request.Url.PathAndQuery, message, this.Application);
            }
            catch
            {
            }
        }

        protected virtual void Application_Error(object sender, EventArgs e)
        {
            var lastError = HttpContext.Current.Server.GetLastError();
            var lastErrorAsHttp = lastError as HttpException;
            if (lastErrorAsHttp != null && lastErrorAsHttp.GetHttpCode() == 404 && new string[] { "options", "head" }.Contains(Request.HttpMethod.ToLower()))
            {
                return;
            }
            var account = BlobService.GetCurrentCloudAccount();
            var guid = Guid.NewGuid();
            LogError(account, guid.ToString() + "\n" + HttpContext.Current.Server.GetLastError().ToString());
            
            if (!Response.BufferOutput) return;
            if (Context.IsCustomErrorEnabled)
            {
                Response.ClearHeaders();
                Response.ClearContent();
                Response.ContentType = "text/html; charset=UTF-8";
                var errorHtml = RenderErrorToString(guid, lastError);
                var statusCode = 500;
                var httpException = lastError as HttpException;
                if (httpException != null) statusCode = httpException.GetHttpCode();

                Response.StatusCode = statusCode;
                if (Request.Cookies.AllKeys.Contains("api"))
                {
                    Response.Write(lastError.Message);
                }
                else
                {
                    if (this.Context.Items.Contains("Download"))
                    {
                        Response.SetCookie(new HttpCookie("errormessage", Uri.EscapeDataString(lastError.Message)));
                    }
                    Response.Write(errorHtml);
                }
                Response.End();
                // エラー状態をクリアする。しないとASP.NETの処理が走る
                Server.ClearError();

            }
        }

        private string RenderErrorToString(Guid id, Exception vm)
        {
            RouteData routeData = new RouteData();
            routeData.Values.Add("controller", "Helper");
            routeData.Values.Add("action", "Error");
            routeData.Values.Add("area", "");
            routeData.Values.Add("transfer", true);

            var request = new RequestContext(new HttpContextWrapper(this.Context), routeData);
            IController controller = ControllerBuilder.Current.GetControllerFactory().CreateController(request, "Helper");
            var context = new ControllerContext(request, (ControllerBase)controller);
            var result = ViewEngines.Engines.FindView(context, "Error", null);

            if (result.View != null)
            {
                StringBuilder sb = new StringBuilder();
                using (StringWriter sw = new StringWriter(sb))
                {
                    using (HtmlTextWriter output = new HtmlTextWriter(sw))
                    {
                        ViewDataDictionary viewData = new ViewDataDictionary();
                        viewData.Add("Id", id);
                        viewData.Model = vm;
                        ViewContext viewContext = new ViewContext(context, result.View, viewData, new TempDataDictionary(), output);
                        result.View.Render(viewContext, output);
                    }
                }

                return sb.ToString();
            }
            return String.Empty;
        }

        protected virtual void Application_BeginRequest()
        {
            this.Context.Items["TraceId"] = Guid.NewGuid().ToString();
            this.Context.Items["TraceStart"] = DateTime.UtcNow;
            Trace.WriteLine("");
            Trace.WriteLine("===== BEGIN =====" + (string)this.Context.Items["TraceId"]);
            Trace.WriteLine(this.Request.HttpMethod);
            Trace.WriteLine(this.Request.Url.ToString());
            foreach (var key in this.Request.Headers.AllKeys)
            {
                Trace.WriteLine(key + ":" + this.Request.Headers[key]);
            }
            foreach (var key in this.Request.Cookies.AllKeys)
            {
                Trace.WriteLine(key + ":" + this.Request.Cookies[key].Value);
            }

        }

        protected virtual void Application_OnEndRequest(Object sender, EventArgs e)
        {
            HttpApplication app = (HttpApplication)sender;

            Trace.WriteLine("Response.StatusCode:" + app.Response.StatusCode.ToString());
            if (this.Context.Items.Contains("TraceStart"))
            {
                var delta = DateTime.UtcNow.Subtract(((DateTime)this.Context.Items["TraceStart"]));
                Trace.WriteLine("Total time:" + delta.TotalSeconds.ToString() + "s");
            }
            Trace.WriteLine("===== END =====" + (string)this.Context.Items["TraceId"] ?? "");


        }

    }
}
